import os
import json
import traceback

import core
import package
import data

class Runtime:
    def __init__(self, app_path:str) -> None:
        self.scope = {}
        self.script = ""
        self.dirs = {}
        self.load_script(app_path)
        self.init_scope(app_path)

    def load_script(self, app_path:str) -> None:
        try:
            with open(app_path, "r") as f:
                self.script = f.read()
        except OSError as e:
            raise core.AppError("runtime_load_app_path_invalid") from e
        
    def init_scope(self, app_path:str) -> None:
        self.APP_DIR = self.scope["APP_DIR"] = os.path.dirname(app_path)

        self.scope["run"] = self.run_package
        self.scope["line"] = self.line
        self.scope["get_package"] = self.get_package
        self.scope["use_dir"] = self.use_dir
        self.scope["get_dir"] = self.get_dir
        self.scope["get_dir_files"] = self.get_dir_files

    def run(self) -> None:
        try:
            exec(self.script, self.scope)
        except Exception as e:
            print (traceback.format_exc())
            raise core.AppError("runtime_running_script_error") from e

    def run_package(self, name:str, arguments) -> None:
        target = os.path.join(data.get_packages_path(),name)
        env = os.path.join(data.get_environments_path(),name)
        if not package.is_valid_package(target):
            raise core.AppError("runtime_running_run_invalid_package")
        if not package.is_valid_environment(env):
            raise core.AppError("runtime_running_run_no_environment")
        run_package = package.run_package(env, target)

        arg = [self.line(str(l)) for l in arguments]

        run_package.stdin.writelines(arg)
        run_package.stdin.close()
        run_package.wait()
        while True:
            if run_package.poll() != None: break

        return run_package.stdout.read().decode()
    
    def line(self, text:str) -> None:
        return (text + "\n").encode()
    
    def get_package(self, pkg:str) -> None:
        target = os.path.join(data.get_packages_path(),pkg)
        if not package.is_valid_package(target):
            raise core.AppError("runtime_running_run_invalid_package")
        return target
    
    def use_dir(self, dirs:list) -> None:
        for dir in dirs:
            self.dirs[dir] = os.path.join(self.APP_DIR,dir)
            os.makedirs(self.dirs[dir], exist_ok=True)
    
    def get_dir(self, dir:str) -> str:
        try:
            return self.dirs[dir]
        except KeyError as e:
            raise core.AppError("dir_not_required:{}".format(dir))

    def get_dir_files(self, dir:str) -> list:
        d = self.get_dir(dir)
        return [os.path.join(d, item) for item in os.listdir(d) if not os.path.isdir(os.path.join(d,item))]

def get_app_data(app_dir:str):
    config_path = os.path.join(app_dir, "app.json")
    try:
        with open(config_path) as f:
            config = json.load(f)
        entrance = config["entrance"]
    except OSError as e:
        raise core.AppError("run_app_config_not_found") from e
    except json.JSONDecodeError as e:
        raise core.AppError("run_app_config_invalid") from e
    except KeyError as e:
        raise core.AppError("run_app_config_no_entrance") from e
    dependencies = config.get("dependencies")
    for dependency in dependencies:
        try:
            installed = package.check_if_installed(dependency["name"])
        except KeyError:
            continue
        if not installed:
            raise core.AppError("run_app_config_dependency_required:{}:{}".format(dependency["name"],dependency["repository"]))
    
    file = os.path.join(app_dir, entrance)
    return file